package com.hainingtong.user.controller;

import com.google.gson.Gson;
import com.hainingtong.common.utils.Captcha;
import com.hainingtong.common.utils.RedisUtils;
import com.hainingtong.user.config.JwtProperties;
import com.hainingtong.user.constant.Constants;
import com.hainingtong.user.dto.*;
import com.hainingtong.user.dto.group.DefaultGroup;
import com.hainingtong.user.dto.group.PhoneCodeGroup;
import com.hainingtong.user.dto.group.PhonePasswordGroup;
import com.hainingtong.user.dto.group.UsernamePasswordGroup;
import com.hainingtong.user.entity.SysUser;
import com.hainingtong.user.entity.UserWithRolesAndPermissions;
import com.hainingtong.user.exception.NicknameExistsException;
import com.hainingtong.user.exception.PrincipalExistsException;
import com.hainingtong.user.service.UserService;
import com.hainingtong.user.shiro.token.LoginType;
import com.hainingtong.user.shiro.token.TokenUtil;
import com.hainingtong.user.util.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.hibernate.validator.constraints.Range;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;


@Api(tags = "用户接口")
@RestController
@Slf4j
@RequestMapping("/api/v1/user")
public class UserController {

    @Autowired
    RedisUtils redisUtils;

    @Autowired
    JwtProperties jwtProperties;

    @Autowired
    UserService userService;

    @Autowired
    Captcha captcha;


    @ApiOperation("测试接口")
    @RequiresRoles({"admin"})
    @RequiresPermissions({"user:delete"})
    @GetMapping("/test")
    Result test() {
        return Result.ok();
    }

    @RequiresAuthentication
    @GetMapping("roles-permissions")
    Result getMyRolesAndPermissions() {
        Subject user = SecurityUtils.getSubject();

        int uid = Integer.parseInt(user.getPrincipal().toString());
        UserWithRolesAndPermissions urp = userService.findUserWithRolesAndPermissions(uid);
        return Result.ok(urp);
    }


    @RequestMapping("/captcha")
    public Result captcha() {
        Map<String, String> ret = captcha.create();
        if (ret == null) {
            return Result.error(ResultCode.CREATE_CAPTCHA_ERROR);
        }

        return Result.ok(ret.get("img"));
    }

    @RequestMapping("/captcha/{code}")
    public Result captchaVerify(@PathVariable String code) {
        if (captcha.verify(code)) {
            return Result.ok();
        }

        return Result.error();
    }




    @ApiOperation(value = "获取注册用户的手机验证码")
    @PostMapping("/register/get/phoneCode")
    Result getRegisterPhoneCode(
            @Validated @RequestBody PhoneDto phoneDto
    ) {
        // 账号已存在就不发验证码了
        if (userService.isUserExists(phoneDto.getPhone(), LoginType.staticPhonePassword)) {
            return Result.error(ResultCode.ERROR_PRINCIPAL_EXISTS);
        }

        if (redisUtils.get(Constants.REDIS_PHONE_CODE_REGISTER_PREFIX + phoneDto.getPhone()) != null) {
            return Result.error(ResultCode.PHONE_CODE_EXISTS);
        }

        // 生成一个4位随机数，作为验证码
        String code = Utils.generateCode();

        // 调用接口发送验证码
        log.info("手机验证码：{}", code); // 此处简单的输出

        // 保存验证码到redis
        redisUtils.set(Constants.REDIS_PHONE_CODE_REGISTER_PREFIX + phoneDto.getPhone(), code, 2L, TimeUnit.MINUTES);

        // 返回验证码和编号到前端，编号在注册的时候要带上，用于从redis中查询手机验证码来比较
        return Result.ok();
    }

    /**
     * 应该改成手机 密码注册
     *
     * @param registerUserDto
     * @return
     * @throws Exception
     */
    @ApiOperation(value = "通过手机号注册账号")
    @PostMapping("/register/PhoneCode")
    Result registerPhoneCode(
            @Validated({PhoneCodeGroup.class, DefaultGroup.class})
            @RequestBody RegisterUserPhoneCodeDto registerUserDto
    ) throws Exception {
        // 从redis获取验证码
        String code = (String) redisUtils.get(Constants.REDIS_PHONE_CODE_REGISTER_PREFIX + registerUserDto.getPrincipal());

        // 比较验证码是否正确
        if (!registerUserDto.getCode().equals(code)) {
            return Result.error(ResultCode.ERROR_PHONE_CODE_INVALID);
        }

        // 手机注册统一保存成，手机密码PhonePassword，
        // 注意登录的时候，PhoneCode和PhonePassword登录都统一成PhonePassword,因为记录的是同一种类型
        registerUserDto.setType(LoginType.staticPhonePassword);
        return register(registerUserDto);
    }

    /**
     * 约定/register/后面跟验证类型，方便前端拼接api请求地址
     *
     * @param registerUserDto
     * @return
     * @throws Exception
     */
    @ApiOperation(value = "使用用户名密码注册账号")
    @PostMapping("/register/UsernamePassword")
    Result registerUsernamePassword(
            @Validated({UsernamePasswordGroup.class, DefaultGroup.class})
            @RequestBody RegisterUserDto registerUserDto) throws Exception {
        registerUserDto.setType(LoginType.staticUsernamePassword);

        try {
            return register(registerUserDto);
        } catch (NicknameExistsException e) {
            return Result.error(ResultCode.ERROR_NICKNAME_EXISTS);
        } catch (PrincipalExistsException e) {
            return Result.error(ResultCode.ERROR_PRINCIPAL_EXISTS);
        }
    }

    Result register(RegisterUserDto registerUserDto) throws Exception {

        Object user = userService.register(
                registerUserDto.getNickname().trim(),
                registerUserDto.getPrincipal(),
                registerUserDto.getCredentials(),
                registerUserDto.getType());

        return Result.ok(ResultCode.SUCCESS_REGISTER, user);

    }

    @ApiOperation(value = "微信小程序注册账号")
    @PostMapping("/register/mini/code")
    Result registerMiniCode(@RequestBody RegisterUserMiniCode userInfo) throws Exception {
        String appId = "wxbd5ca687b14d5743";
        String secretKey = "0ee8c0def1c8d7901707facd7fdcb29d";

        String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
                appId, secretKey, userInfo.getCode());

        try {
            HttpClient client = new DefaultHttpClient();
            //发送get请求
            HttpGet request = new HttpGet(url);
            HttpResponse response = client.execute(request);

            /**请求发送成功，并得到响应**/
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                /**读取服务器返回过来的json字符串数据**/
                String strResult = EntityUtils.toString(response.getEntity());
                Gson gson = new Gson();
                Map<String, String> res = gson.fromJson(strResult, Map.class);
                log.info(strResult);
                log.info(res.get("openid"));

                RegisterUserDto registerUserDto = new RegisterUserDto();
                registerUserDto.setNickname(userInfo.getNickname());
                registerUserDto.setPrincipal(res.get("openid"));
                registerUserDto.setType(LoginType.staticMiniLogin);

                // 尝试多次生成新昵称
                for (int i = 0; i < 100; i++) {
                    try {
                        return register(registerUserDto);
                    } catch (NicknameExistsException e) {
                        registerUserDto.setNickname(userInfo.getNickname() + new Random().nextInt(99999));
                    } catch (PrincipalExistsException e) {
                        return Result.error(ResultCode.ERROR_PRINCIPAL_EXISTS);
                    }
                }

                return Result.error(ResultCode.ERROR_PRINCIPAL_EXISTS);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return Result.ok();
    }

    @ApiOperation(value = "登录账号时获取验证码")
    @PostMapping("/login/get/phoneCode")
    Result getLoginPhoneCode(
            @ApiParam("手机号")
            @Validated @RequestBody PhoneDto phoneDto
    ) {

        // 用户存在才发验证码
        boolean b = userService.isUserExists(phoneDto.getPhone(), LoginType.staticPhonePassword);

        // 账号不存在 就不发送验证码
        if (!b) {
            return Result.error(ResultCode.ERROR_PRINCIPAL_NOT_EXISTS);
        }

        if (redisUtils.get(Constants.REDIS_PHONE_CODE_LOGIN_PREFIX + phoneDto.getPhone()) != null) {
            return Result.error(ResultCode.PHONE_CODE_EXISTS);
        }

        // 生成一个4位随机数，作为验证码
        String code = Utils.generateCode();

        // 调用接口发送验证码
        log.info("手机验证码：{}", code); // 此处简单的输出

        // 保存验证码到redis
        redisUtils.set(Constants.REDIS_PHONE_CODE_LOGIN_PREFIX + phoneDto.getPhone(), code, 2L, TimeUnit.MINUTES);

        // 返回验证码和编号到前端，编号在注册的时候要带上，用于从redis中查询手机验证码来比较
        return Result.ok();
    }

    @ApiOperation(value = "手机验证码登录")
    @PostMapping("/login/PhoneCode")
    Result loginPhoneCode(
            @Validated({PhoneCodeGroup.class, DefaultGroup.class})
            @RequestBody LoginPhoneCodeDto loginPhoneCodeDto) throws Exception {

        // 从redis获取验证码
        String code = (String) redisUtils.get(Constants.REDIS_PHONE_CODE_LOGIN_PREFIX + loginPhoneCodeDto.getPrincipal());

        // 比较验证码是否正确
        if (!loginPhoneCodeDto.getCode().equals(code)) {
            return Result.error(ResultCode.ERROR_PHONE_CODE_INVALID);
        }

        // 验证码正确，删除登陆验证码，防止被暴力攻击
        redisUtils.remove(Constants.REDIS_PHONE_CODE_LOGIN_PREFIX + loginPhoneCodeDto.getPrincipal());

        // 注意登录的时候，PhoneCode和PhonePassword登录都统一成PhonePassword,因为记录的是同一种类型
        loginPhoneCodeDto.setType(LoginType.PhonePassword.name());

        // false表示不需要登录验证，因为验证码通过就表示登录成功了，在获取验证码的时候就保证了用户是存在的。
        return login(loginPhoneCodeDto, false);
    }


    @ApiOperation(value = "手机密码登录")
    @PostMapping("/login/PhonePassword")
    Result loginPhonePassword(
            @Validated({PhonePasswordGroup.class, DefaultGroup.class})
            @RequestBody LoginUserDto loginUserDto) {

        // 注意登录的时候，PhoneCode和PhonePassword登录都统一成PhonePassword,因为记录的是同一种类型
        loginUserDto.setType(LoginType.staticPhonePassword);

        return login(loginUserDto);
    }

    @ApiOperation(value = "用户名密码登录")
    @PostMapping("/login/UsernamePassword")
    Result loginUsernamePassword(
            @Validated({UsernamePasswordGroup.class, DefaultGroup.class})
            @RequestBody LoginUserDto user
    ) {
        user.setType(LoginType.staticUsernamePassword);
        return login(user);
    }

    Result login(LoginUserDto user) {
        return login(user, true);
    }

    /**
     * 执行登录操作
     *
     * @param user
     * @param loginVerify ture表示会执行登录操作，进入到realm里面； false表示 不验证，直接生成 token 和 用户信息
     * @return
     */
    Result login(LoginUserDto user, boolean loginVerify) {

        if (loginVerify) {
            Subject subject = SecurityUtils.getSubject();

            // 根据用户数据创建不同类型的 token
            AuthenticationToken token = TokenUtil.createToken(
                    user.getPrincipal(),
                    user.getCredentials(),
                    LoginType.valueOf(user.getType())
            );

            try {
                //进行验证，这里可以捕获异常，然后返回对应信息
                subject.login(token);
            } catch (UnknownAccountException e) {
                log.warn("账号不存在！", e);
                return Result.error(ResultCode.ERROR_PRINCIPAL_NOT_EXISTS);
            } catch (IncorrectCredentialsException e) {
                log.warn("账号或密码错误！", e);
                return Result.error(ResultCode.ERROR_PASSWORD);
            } catch (AuthorizationException e) {
                log.warn("没有权限！", e);
                return Result.error(ResultCode.ERROR_NO_PERMISSION);
            } catch (AuthenticationException e) {
                log.error("其他错误：", e);
                return Result.error(ResultCode.UNKNOWN_REASON, e.getMessage());
            }
        }
        SysUser u = userService.findUserByPrincipalAndType(user.getPrincipal(), user.getType());

        if (u.isDisabled()) {
            return Result.error(ResultCode.ERROR_USER_DISABLED);
        }

        Map<String, String> d = new HashMap<>();
        // 带上userId,授权的时候不用查数据库
        d.put("uid", u.getId() + "");

        // 也可以把权限和角色查询出来放到token中

        // 生成jwt token返回给客户端
        String tokenStr = JwtUtil.sign(d, jwtProperties.getSecretKey());
        HashMap<String, Object> data = new HashMap<>();
        data.put("token", tokenStr);
        data.put("name", u.getNickname());
        data.put("avatar", u.getAvatar());
        return Result.ok(ResultCode.SUCCESS_LOGIN, data);
    }


    @ApiOperation("用户列表")
    @GetMapping("/list")
    public Result getAllUsers(@RequestParam(defaultValue = "1") int page,
                              @Validated
                              @Range(min = 1, max = 100, message = "每页最多100条")
                              @RequestParam(defaultValue = "20") int size
    ) {

        return Result.ok(userService.getUsers(page, size));
    }

    @ApiOperation("获取所有权限列表")
    @GetMapping("/permissions")
    public Result getPermissions() {
        return Result.ok(userService.findAllPermissions());
    }

    @ApiOperation("获取所有角色列表")
    @GetMapping("/roles")
    public Result getRoles() {
        return Result.ok(userService.findAllRoles());
    }

}
